package com.maben.receiver;

import cn.hutool.core.util.HexUtil;
import com.maben.receiver.wrapper.SnmpV2Message;
import com.maben.receiver.wrapper.SnmpV2Pdu;
import com.maben.receiver.wrapper.SnmpV2cAddress;
import com.maben.receiver.wrapper.SnmpVariableBinding;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.snmp4j.CommunityTarget;
import org.snmp4j.PDU;
import org.snmp4j.Snmp;
import org.snmp4j.TransportMapping;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.smi.*;
import org.snmp4j.transport.DefaultUdpTransportMapping;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.CountDownLatch;

/**
 * 创建监听
 */
@Slf4j
public class SnmpV2cReceiver {
    // SNMP v2c/v3 中存储 enterprise OID 的固定位置
    private static final OID SNMP_TRAP_OID = new OID("1.3.6.1.6.3.1.1.4.1.0");
    /**
     * 开启监听
     */
    public static void startTrapReceiver(Integer port, SnmpV2cReceiverTrapListener trapListener) throws Exception {
        //new UdpAddress(port)等价于new UdpAddress("0.0.0.0/" + port)，其中 0.0.0.0 表示【绑定本机所有网络接口的 IP 地址】。
        TransportMapping<UdpAddress> transport = new DefaultUdpTransportMapping(new UdpAddress(port));
        Snmp snmpTrap = new Snmp(transport);
        transport.listen();
        snmpTrap.addCommandResponder((event) -> {
            try {
                // 获取SNMP消息基本信息
                final Address address = event.getPeerAddress();
                // 解析版本和共同体名
                int version = event.getSecurityModel(); // SNMPv2c对应MPv2c.SECURITY_MODEL
                String community = resolveCommunity(event.getSecurityName());
                SnmpV2Message snmpMessage = new SnmpV2Message();
                snmpMessage.setCommunity(community);
                snmpMessage.setVersion(version);
                snmpMessage.setSnmpV2cAddress(getSnmpV2cAddress(address));
                // 解析PDU字段
                PDU pdu = event.getPDU();
                if (pdu == null) return;
                //处理TRAP消息
                handleSnmpV2cTrap(pdu, snmpMessage, trapListener);
                // 对INFORM类型需要回复确认
                handleSnmpV2cInform(pdu, community, address, snmpTrap);
            } catch (Exception e) {
                log.error("===handle snmp response error:{}", ExceptionUtils.getStackTrace(e));
            }

        });
        log.info("=====启动SNMP监听成功:{}====",port);
    }

    /**
     * 处理Inform消息，需要中心服务端回复的
     */
    private static void handleSnmpV2cInform(PDU pdu, String community, Address address, Snmp snmpTrap)throws Exception {
        if (pdu.getType() != PDU.INFORM) return;
        // 手动创建响应PDU
        PDU response = new PDU();
        response.setType(PDU.RESPONSE);
        response.setRequestID(pdu.getRequestID());
        response.setErrorIndex(0);
        response.setErrorStatus(0);
        response.setVariableBindings(pdu.getVariableBindings());
        // 手动创建target
        CommunityTarget target = new CommunityTarget();
        target.setCommunity(new OctetString(community));
        target.setVersion(SnmpConstants.version2c);
        target.setAddress(address);
        // 发送响应
        snmpTrap.send(pdu, target);
    }

    /**
     * 处理【网络设备主动向管理端报告事件】
     */
    private static void handleSnmpV2cTrap(PDU pdu, SnmpV2Message snmpMessage, SnmpV2cReceiverTrapListener trapListener) {
        if (pdu.getType() != PDU.TRAP) return;
        // 提取 enterprise OID（从 snmpTrapOID.0 中获取）
        int type = pdu.getType();
        int requestId = pdu.getRequestID().toInt();
        int errorStatus = pdu.getErrorStatus();
        int errorIndex = pdu.getErrorIndex();
        // 解析变量绑定列表
        List<SnmpVariableBinding> variableBindings = new ArrayList<>();
        for (VariableBinding vb : pdu.getVariableBindings()) {
            String oid = vb.getOid().toString();
            Variable variable = vb.getVariable();
            String valueType = getValueType(variable);
            Object value = getValue(variable);
            variableBindings.add(new SnmpVariableBinding(oid, valueType, value));
        }
        // 装填到封装类
        SnmpV2Pdu snmpPdu = new SnmpV2Pdu(type, requestId, errorStatus, errorIndex, variableBindings);
        snmpMessage.setPdu(snmpPdu);
        trapListener.handleSnmpV2Message(snmpMessage);
    }

    /**
     * 遍历变量绑定列表，查找snmpTrapOID.0对应的enterprise OID
     *   [企业根 OID] + [厂商自定义的陷阱编号]
     *   示例：1.3.6.1.2.1.192.0.1：【1.3.6.1.2.1.192】+【.0.1】
     */
    private static OID findEnterpriseOid(Vector<? extends VariableBinding> variableBindings) {
        for (VariableBinding vb : variableBindings) {
            // 比较当前变量的OID是否等于目标OID
            if (vb.getOid().equals(SNMP_TRAP_OID)) {
                Variable variable = vb.getVariable();
                if (variable instanceof OID) {
                    return (OID) variable;
                }
            }
        }
        return null;
    }


    /**
     * 获取地址封装类
     */
    private static SnmpV2cAddress getSnmpV2cAddress(Address address) {
        // 判断是否为InetSocketAddress类型（网络通信中最常见）
        if (address instanceof InetSocketAddress) {
            InetSocketAddress socketAddress = (InetSocketAddress) address;
            // 获取IP地址（InetAddress对象）
            InetAddress inetAddress = socketAddress.getAddress();
            String ip = inetAddress.getHostAddress(); // 转换为字符串形式的IP
            int port = socketAddress.getPort();
            return new SnmpV2cAddress(ip, port);
        }
        if (address instanceof UdpAddress) {
            UdpAddress udpAddress = (UdpAddress) address;
            final InetAddress inetAddress = udpAddress.getInetAddress();
            String ip = inetAddress.getHostAddress(); // 转换为字符串形式的IP
            int port = udpAddress.getPort();
            return new SnmpV2cAddress(ip, port);
        }
        // 处理其他类型的Address（如本地进程通信的地址等）
        log.warn("===不支持的Address类型: " + address.getClass().getName());
        return null;
    }

    /**
     * 辅助方法
     *      解析共同体名（处理byte[]转字符串）
     *      SNMPv2c的共同体名字符编码通常为UTF-8或ASCII
     */
    private static String resolveCommunity(byte[] securityNameBytes) {
        // 处理null情况（避免空指针）
        if (securityNameBytes == null) {
            return "";
        }
        // 尝试常见编码解析
        Charset[] possibleCharsets = {
                StandardCharsets.US_ASCII,
                StandardCharsets.UTF_8,
                Charset.forName("ISO-8859-1")
        };

        for (Charset charset : possibleCharsets) {
            try {
                // 验证编码有效性
                charset.newDecoder().decode(java.nio.ByteBuffer.wrap(securityNameBytes));
                return new String(securityNameBytes, charset);
            } catch (CharacterCodingException e) {
                continue; // 编码不匹配，尝试下一种
            }
        }

        // 所有编码失败时返回十六进制
        return HexUtil.encodeHexStr(securityNameBytes);
    }

    /**
     * 辅助方法：获取SNMP值类型
     */
    private static String getValueType(Variable variable) {
        // 获取变量实际类型的简单类名（如Integer32、OctetString等）
        String className = variable.getClass().getSimpleName();
        // 过滤可能的代理类或包装类（如果有特殊情况）
        if (className.contains("$")) {
            return "Unknown";
        }
        return className;
    }


    /**
     *  辅助方法：将SNMP变量转换为Java对象
     */
    private static Object getValue(Variable variable) {
        if (variable instanceof Integer32) return ((Integer32) variable).getValue();
        if (variable instanceof OctetString) return ((OctetString) variable).toString();
        if (variable instanceof IpAddress) return ((IpAddress) variable).getInetAddress().getHostAddress();
        if (variable instanceof Counter32) return ((Counter32) variable).getValue();
        if (variable instanceof Gauge32) return ((Gauge32) variable).getValue();
        return variable.toString();
    }

        public static void main(String[] args) throws Exception {
            final CountDownLatch latch = new CountDownLatch(10);
            SnmpV2cReceiver.startTrapReceiver(162, message -> {
                latch.countDown();
                final SnmpV2cAddress snmpV2cAddress = message.getSnmpV2cAddress();
                if (snmpV2cAddress != null) {
                    System.out.println("address: " + snmpV2cAddress.getIp() + " : " + snmpV2cAddress.getPort());
                }
                final SnmpV2Pdu pdu = message.getPdu();
                System.out.println("收到消息，类型: " + pdu.getType());
                for (SnmpVariableBinding variableBinding : pdu.getVariableBindings()) {
                    System.out.println(variableBinding.getOid() + " : " + variableBinding.getValueType() + " : " + variableBinding.getValue());
                }
            });
            latch.await();
        }
}
